#region Copyright Vanderlande Industries B.V. 2009-2011
//
// Copyright (c) 2009-2011 Vanderlande Industries.
// All rights reserved.
//
// @file:     FscConfigurationReader.cs
// @version:  %version: 29 %
// %date_created: vrijdag 3 augustus 2012 8:21:07 %
//
// The copyright to the computer program(s) herein is the property of
// Vanderlande Industries. The program(s) may be used and/or copied
// only with the written permission of the owner or in accordance with
// the terms and conditions stipulated in the contract under which the
// program(s) have been supplied.
#endregion

using System;
using System.IO;
using System.Collections.Generic;
using System.Reflection;
using System.Threading;
using System.Xml;
using System.Windows.Forms;
//using VI.Bfw.Logging;
using VI.Fsc.OperatorGui.WebReferences.Opc;
using VI.Fsc.OperatorGui.Models.Application;
using VI.Fsc.OperatorGui.Models.Application.Util;
using VI.Fsc.Common.FileTransfer;
using VI.Fsc.Common.Fsc;
using System.Data;

namespace VI.Fsc.OperatorGui.Models.Fsc.Configuration
{
    // Handlers for the reader.
    public delegate void ReadRemoteFscConfigurationUpdateEventHandler();
    public delegate void ReadRemoteFscConfigurationFinishEventHandler(int maximumStartupProgress);

    public class CFscStructureReader
    {
        public event ReadRemoteFscConfigurationUpdateEventHandler ReadRemoteFscStructureUpdateEvent;
        public event ReadRemoteFscConfigurationFinishEventHandler ReadRemoteFscStructureFinishEvent;

        private bool readFromXmlEnabled = false;

        private Dictionary<string, CStructureItem> structureTree;

        public Dictionary<string, CStructureItem> StructureTree
        {
            get { return structureTree; }
        }

        /// <summary>
        /// Singleton instance of the reader.
        /// </summary>
        private static CFscStructureReader instance;

        private Thread readRemoteFscStructureThread;

        /// <summary>
        /// Counter variable to keep track of the number of steps done during the
        /// load of the fsc configuration. Used to update the maximum progress value 
        /// the next time the module runs. 
        /// </summary>
        private int structureLoadStepsCounter = 0;

        /// <summary>
        /// Getter for the singleton instance of the service.
        /// </summary>
        public static CFscStructureReader Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new CFscStructureReader();
                }

                return instance;
            }
        }

        public CFscStructureReader()
        {
            this.structureTree = new Dictionary<string, CStructureItem>();
        }

        /// <summary>
        /// Notifies that the execution of the read of the fsc remote
        /// configuration did some progress.
        /// </summary>
        private void NotifyReadRemoteFscStructureUpdate()
        {
            // Updates the number of update steps done during this process.
            structureLoadStepsCounter++;

            if (null != ReadRemoteFscStructureUpdateEvent)
            {
                ReadRemoteFscStructureUpdateEvent();
            }
        }

        /// <summary>
        /// Actions done once the fsc configuration load is completed:
        ///     1- notify about completion
        ///     2- show errors inmediately (don't wait for the subscription, to prevent delays)
        ///     3- start the subscription on all fsc components
        /// </summary>
        private void NotifyReadRemoteFscStructureFinish()
        {
            // Initialization of the structureTree has finished. Check whether a debug file needs to be saved.
            if (CFscConfiguration.Instance.EnableDebugFile)
            {
                debugPrintStructureTreeToFile();
            }

            if (null != ReadRemoteFscStructureFinishEvent)
            {
                ReadRemoteFscStructureFinishEvent(structureLoadStepsCounter);
            }
        }

        /// <summary>
        /// Stops the server activity, by aborting all currently alive threads.
        /// </summary>
        public void StopServerActivity()
        {
            //Logger.Entering(MethodBase.GetCurrentMethod());

            int waitMilliseconds = 200;
            if ((null != this.readRemoteFscStructureThread) && this.readRemoteFscStructureThread.IsAlive)
            {
                //Logger.Debug(MethodBase.GetCurrentMethod(), "Trying to stop thread " +
                    //this.readRemoteFscStructureThread.ManagedThreadId + " gracefully.");
                bool stopped = this.readRemoteFscStructureThread.Join(waitMilliseconds);
                if (!stopped)
                {
                    try
                    {
                        this.readRemoteFscStructureThread.Abort();
                    }
                    catch (ThreadAbortException ex)
                    {
                        //Logger.Info(MethodBase.GetCurrentMethod(), "Thread aborted: " + ex.Message);
                    }
                }
            }

            //Logger.Exiting(MethodBase.GetCurrentMethod());
        }

        /// <summary>
        /// Start reading the configuration in a new thread. Store the value that determines whether to load
        /// from xml or opc.
        /// </summary>
        /// <param name="forceLoadFromOPC">If true, then the FscStructure will always be read from OPC. If false,
        /// then the configuration settings will be used</param>
        public void ReadRemoteFscStructure(bool forceLoadFromOPC = false)
        {
            // The configurating setting compined with the forceLoadFromOpc parameter determine whether to load
            // the fsc structrure from the xml file or from the opc server.
            readFromXmlEnabled = (CFscConfiguration.Instance.LoadOpcStructureFromXml  && !forceLoadFromOPC);

            // Before creating a new ReadRemoteFscStructureThread, make sure the old one is stopped
            StopServerActivity();

            // Loads the layout configuration in a new thread. 
            readRemoteFscStructureThread = new Thread(ReadRemoteFscStructureThread);
            readRemoteFscStructureThread.IsBackground = true;
            readRemoteFscStructureThread.Start();
        }

        private void ReadRemoteFscStructureThread()
        {
            // Check which fsc structure read to call
            if (readFromXmlEnabled)
            {
                readFscStructureFromXmlThread();
            }
            else
            {
                readFscStructureFromOpcThread();
            }
            // Notify listeners about read finish.
            NotifyReadRemoteFscStructureFinish();
        }

        private void readFscStructureFromXmlThread()
        {
            try
            {
                bool xmlConfigIsLoaded = false;

                // TODO: at the moment we really only handle 1 fsc per GUI. Changes will
                // be needed all around (including here) to handle more than 1 FSC.
                // Retrieve the xml file to read and check whether it exists.
                string xmlConfigFile = CFileConstants.OGuiLocalDiagnosticsFilesPath + CFileConstants.XmlConfigLoadFile;

                if (System.IO.File.Exists(xmlConfigFile))
                {
                    // 1. Clean/Initialize the data structure.
                    structureTree.Clear();

                    // 2. Clean the memory data structures.
                    CFscConfiguration.Instance.ClearConfiguration();

                    // 3. Create the xml reader.
                    XmlConfigReader reader = new XmlConfigReader();
                    if (reader.ReadXmlConfig(xmlConfigFile))
                    {
                        // 4. Perform the actual read.
                        //Logger.Debug(MethodBase.GetCurrentMethod(), "StartReadTreeThread() - Load configuration from xml file");
                        xmlConfigIsLoaded = ReadTreeFromFile(reader);
                    }
                }

                // If reading the xml file failed for any reason, use the old method to intialize the structure by reading OPC
                if (!xmlConfigIsLoaded)
                {
                    //Logger.Debug(MethodBase.GetCurrentMethod(), "Failed to read fsc structure from xml file, start reading from opc directly.");
                    readFscStructureFromOpcThread();
                }           
            }
            catch (ThreadAbortException e)
            {
                //Logger.Info(MethodBase.GetCurrentMethod(), "Trying to abort thread. " + e);
            }
            catch (Exception ex) // As in the RCF-Main program we exit if an exception arrives unhandled here.
            {
                //Logger.Fatal(MethodBase.GetCurrentMethod(), "Uncaught exception occurred.", ex);
                MessageBox.Show("Uncaught exception occurred: " + ex.Message + "\nSee log for details.", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                Environment.Exit(1);
            }
        }
   
        /// <summary>
        /// This method can be called to dump the UI's internal representation of the fsc structure to a log file. This
        /// log file can be used to compare the UI's representation to the opc structure.
        /// </summary>
        private void debugPrintStructureTreeToFile()
        {
            try
            {
                string fullfilename = Environment.CurrentDirectory + @"\";
                fullfilename += (CFscConfiguration.Instance.LoadOpcStructureFromXml) ? "StartupDebugXml.log" : "StartupDebugOpc.log";
                SortedDictionary<string, CStructureItem> sortedTree = new SortedDictionary<string, CStructureItem>(structureTree);
                StreamWriter sw = File.CreateText(fullfilename);

                foreach (var item in sortedTree)
                {
                    sw.WriteLine("{0}: (ID={1},PARENTID={2},VALUE={3})", item.Key, item.Value.Id, item.Value.ParentId, item.Value.Value);
                }

                sw.Flush();
                sw.Close();
            }
            catch (Exception ex)
            {
                //Logger.Error(MethodBase.GetCurrentMethod(), "Exception while trying to write the Structure Tree logfile.", ex);
            }
        }

        /// <summary>
        /// Read the items from the xml file and store each item in the UI's internal representation of the fsc structure.
        /// </summary>
        /// <param name="xmlConfigReader"></param>
        /// <returns></returns>
        private bool ReadTreeFromFile(XmlConfigReader xmlConfigReader)
        {
            bool result = false;

            try
            {
                List<DataRow> sortedRows = xmlConfigReader.ConfigTable_GetSortedRows();
                // step through all rows in the Config table
                foreach (DataRow row in sortedRows)
                {
                    string name = row.Field<string>(XmlConfigReader.COL_NAME);
                    string path = row.Field<string>(XmlConfigReader.COL_ITEM).Trim('/');
                    string type = row.Field<string>(XmlConfigReader.COL_TYPE);
                    string value = row.Field<string>(XmlConfigReader.COL_VALUE);
                                       
                    if (type != XmlConfigReader.COMPONENT_TYPE)
                    {
                        // Create a structure item for a non-component and add them to the structureTree.
                        string pathAndName = path + "/" + name;
                        structureTree[pathAndName] = new CStructureItem(pathAndName, path, value);

                    }
                    else
                    {
                        // Create a structure item for a component. 
                        if (!string.IsNullOrEmpty(path))
                        {
                            string componentParent = GetParentIdFromId(path);
                            structureTree[path] = new CStructureItem(path, componentParent, null);
                            // Use the component items to notify about progress.
                            NotifyReadRemoteFscStructureUpdate();
                        }
                    }
                }

                // Copy the structureTree to the configuration instnace and notify the listeners that we are finished.
                CFscConfiguration.Instance.StructureTree = structureTree;
                result = true;
            }
            catch (Exception e)
            {
                //Logger.Error(MethodBase.GetCurrentMethod(), "There was an exception while building up the configuration from the xml configuration file", e);
            }

            return result;
        }

        /// <summary>
        /// Get the Parent id of an specified id
        /// </summary>
        /// <param name="id">Id to get the parentId from</param>
        /// <returns>parentId</returns>
        private string GetParentIdFromId(string id)
        {
            int pos = id.LastIndexOf('/');
            if (pos < 1)
            {
                return null;
            }
            else
            {
                return id.Substring(0, pos);
            }
        }

        private void readFscStructureFromOpcThread()
        {
            try
            {
                // TODO: at the moment we really only handle 1 fsc per GUI. Changes will
                // be needed all around (including here) to handle more than 1 FSC.
                // 1. Clean/Initialize the data structure.
                structureTree.Clear();

                // 2. Clean the memory data structures.
                CFscConfiguration.Instance.ClearConfiguration();

                // 3. Get the initial list of elements to be browsed.
                BrowseElement[] initialElementsList = BrowseObjects(null, browseFilter.branch);

                // 4. If we got 0 elements to browse, then the server is not 
                // ready to be used.
                if (initialElementsList.Length == 0)
                {
                    // 5.1. Restart the connection.
                    COPCServerConnectionService.Instance.Init();
                }
                else
                {
                    // 5.2. Loop through the initial list of elemenents, 
                    // saving in memory the structure.
                    foreach (BrowseElement element in initialElementsList)
                    {
                        SaveStructureItem(element, null);
                    }
                }

                // 6. Store the structure tree in the configuration
                CFscConfiguration.Instance.StructureTree = structureTree;
            }
            catch (ThreadAbortException e)
            {
                //Logger.Info(MethodBase.GetCurrentMethod(), "Trying to abort thread. " + e);
            }
            catch (Exception ex) // As in the RCF-Main program we exit if an exception arrives unhandled here.
            {
                //Logger.Fatal(MethodBase.GetCurrentMethod(), "Uncaught exception occurred.", ex);
                MessageBox.Show("Uncaught exception occurred: " + ex.Message + "\nSee log for details.", "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                Environment.Exit(1);
            }
        }

        private void SaveStructureItem(BrowseElement element, string parentId)
        {
            string fullPath = element.ItemPath + element.ItemName;
            ExtendedStructureItem structureItem = new ExtendedStructureItem(fullPath, parentId, element.Properties[0].Value, null);

            //Logger.Debug(MethodBase.GetCurrentMethod(), "Saving structure item " + fullPath);
            
            structureTree.Add(fullPath, structureItem);

            if (!element.IsItem && element.HasChildren)
            {
                try
                {
                    foreach (BrowseElement subElement in BrowseObjects(fullPath, browseFilter.all))
                    {
                        SaveStructureItem(subElement, fullPath);
                    }
                }
                catch (Exception e)
                {
                    //Logger.Debug(MethodBase.GetCurrentMethod(), e.Message);
                }
            }
        }

        /// <summary>
        /// Method to browse a branch and return all child branches or elements.
        /// </summary>
        /// <param name="itemName"></param>
        /// <param name="browseType">The type of browsing: branch or all</param>
        /// <returns></returns>
        private BrowseElement[] BrowseObjects(string itemName, browseFilter browseType)
        {
            // We only want to get the value and the timestamp properties.
            XmlQualifiedName[] propertyNames = new XmlQualifiedName[] {new XmlQualifiedName("value")};
            BrowseElement[] returnedElements = new BrowseElement[] { };

            try
            {
                returnedElements =
                    CFscUtility.ExecuteBrowseCall(itemName, browseType, propertyNames);
            }
            catch (System.Net.WebException webException)
            {
                ////Logger.Error(MethodBase.GetCurrentMethod(), webException.Message);
                COPCServerConnectionService.Instance.IsOnline = false;
            }
            catch (Exception e)
            {
                throw e;
            }

            // Increases the progress of the progress indicator by 1 unit.
            NotifyReadRemoteFscStructureUpdate();

            return returnedElements;
        }
    }
}
